/********************************************************************************
 * Copyright (c) 2017, 2018 Bosch Connected Devices and Solutions GmbH.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * Contributors:
 *    Bosch Connected Devices and Solutions GmbH - initial contribution
 *
 * SPDX-License-Identifier: EPL-2.0
 ********************************************************************************/

package org.eclipse.mita.program.generator

import com.google.inject.Inject
import java.util.LinkedList
import java.util.List
import org.eclipse.xtend2.lib.StringConcatenationClient
import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode
import org.eclipse.xtext.generator.trace.node.IGeneratorNode
import org.eclipse.xtext.generator.trace.node.TemplateNode
import org.eclipse.xtext.generator.trace.node.TextNode
import org.eclipse.xtext.generator.trace.node.NewLineNode

/**
 * A code fragment is source code generated by a component generator.
 */
class CodeFragment extends CompositeGeneratorNode {
	static dispatch def String doGetString(IGeneratorNode node) {
		node.toString;
	}
	static dispatch def String doGetString(CompositeGeneratorNode node) {
		node.children.map[doGetString].join; 
	}
	static dispatch def String doGetString(TextNode node) {
		node.text.toString
	}
	static dispatch def String doGetString(NewLineNode node) {
		""
	}
	
	def getString() {
		return doGetString(this);
	}
	
	override toString() {
		return getString();
	}
	
	static class IncludePath {
		public static final int ULTRA_LOW_PRIORITY = 0;
		public static final int VERY_LOW_PRIORITY = 250;
		public static final int LOW_PRIORITY = 500;
		public static final int DEFAULT_PRIORITY = 500;
		public static final int HIGH_PRIORITY = 750;
		public static final int VERY_HIGH_PRIORITY = 1000;
		public static final int ULTRA_HIGH_PRIORITY = 1250;
		
		final String path;
		final boolean systemInclude;
		final int priority;

		new(String path, boolean isSystemInclude) {
			this(path, isSystemInclude, DEFAULT_PRIORITY);
		}

		new(String path, boolean isSystemInclude, int priority) {
			this.path = path;
			systemInclude = isSystemInclude;
			this.priority = priority;
		}

		/**
		 * @return the path
		 */
		def String getPath() {
			return path;
		}

		/**
		 * @return the systemInclude
		 */
		def boolean isSystemInclude() {
			return systemInclude;
		}
		
		override String toString() {
			val opener = if(systemInclude) '<' else '"';
			val closer = if(systemInclude) '>' else '"';
			
			return '''#include «opener»«path»«closer»'''
		}
		
		override hashCode() {
			path.hashCode
		}
		
	}

	public static final CodeFragment EMPTY = new CodeFragment();

	@Inject extension ProgramDslTraceExtensions traceExtension
	
	@Inject
	protected extension GeneratorUtils

	/**
	 * The include paths for header files required by this code fragment.
	 */
	final List<IncludePath> includePaths = new LinkedList;
	
	/**
	 * The global preamble which is added to the beginning of the C file this code will be placed in.
	 */
	IGeneratorNode preamble = null;
	
	/**
	 * Adds the relative path to a header file to the list of includes. 
	 * 
	 * @param path the path to add
	 * @return this code fragment
	 */
	def CodeFragment addHeader(String path, boolean isSystemInclude) {
		val includePath = new IncludePath(path, isSystemInclude);
		this.includePaths.add(includePath);
		return this;
	}
	
	/**
	 * Adds the relative path to a header file to the list of includes. 
	 * 
	 * @param path the path to add
	 * @return this code fragment
	 */
	def CodeFragment addHeader(String path, boolean isSystemInclude, int priority) {
		val includePath = new IncludePath(path, isSystemInclude, priority);
		this.includePaths.add(includePath);
		return this;
	}
	
	/**
	 * Adds a list of include paths to this code fragment. 
	 * 
	 * @param paths the paths to add
	 * @return this code fragment
	 */
	def CodeFragment addHeader(IncludePath... paths) {
		this.includePaths.addAll(paths);
		return this;
	}
	
	/**
	 * Sets the global preamble which is added to the beginning of the C file this code will be placed in.
	 * 
	 * @param preamble the preamble to set
	 * @return this code fragment
	 */
	def CodeFragment setPreamble(IGeneratorNode preamble) {
		this.preamble = preamble;		
		return this;
	}
	
	def CodeFragment setPreamble(StringConcatenationClient preamble) {
		this.preamble = new TemplateNode(preamble, traceExtension);
		return this;
	}

	/**
	 * @return the includePaths
	 */
	def List<IncludePath> getIncludePaths() {
		return includePaths;
	}

	protected def combineIncludes(Iterable<IncludePath> paths) {
		val allIncludes = paths.filterNull;
		val includeMap = allIncludes.groupBy[x | x.toString ];
		val includes = includeMap.values.map[x | x.maxBy[y | y.priority ] ].toList;
		
		return includes.sortBy[x | x.priority * (if(x.isSystemInclude) 10 else 1)].reverse.map[x | x.toString]
	}
	
	def toHeader(CompilationContext context, String guardName) {
		val includes = (includePaths + children.findIncludes).toList;
		val preamble = new CompositeGeneratorNode();
		if(this.preamble !== null) {
			preamble.children += this.preamble
		}
		preamble.children += children.findPreamble;
		includes += #[preamble].findIncludes;
		
		val result = new CompositeGeneratorNode();
		result.children.add(new TemplateNode('''
			«generateHeaderComment(context)»
			
			#ifndef «guardName.toUpperCase»
			#define «guardName.toUpperCase»
			
			«FOR include : includes.combineIncludes»
			«include»
			«ENDFOR»
			
			«preamble»
			
			«this»
			
			#endif
		''', traceExtension));
		return result;
	}
	
	def toImplementation(CompilationContext context) {
		val includes = (includePaths + children.findIncludes).toList;
		val preamble = new CompositeGeneratorNode();
		if(this.preamble !== null) {
			preamble.children += this.preamble
		}
		preamble.children += children.findPreamble;
		includes += #[preamble].findIncludes;
		
		val result = new CompositeGeneratorNode();
		result.children.add(new TemplateNode('''
			«generateHeaderComment(context)»
			
			«FOR include : includes.combineIncludes»
			«include»
			«ENDFOR»
			
			«preamble»
			
			«this»
			
		''', traceExtension));
		return result;
	}
	
	private def Iterable<IncludePath> findIncludes(Iterable<? extends IGeneratorNode> nodes) {
		nodes.filter(CompositeGeneratorNode).map[x | 
			if(x instanceof CodeFragment) {
				x.includePaths + x.children.findIncludes
			} else {
				x.children.findIncludes
			}
		].flatten
	}
	
	private def Iterable<IGeneratorNode> findPreamble(Iterable<IGeneratorNode> nodes) {
		nodes.filter(CompositeGeneratorNode).map[x | 
			if(x instanceof CodeFragment) {
				#[x.preamble] + x.children.findPreamble
			} else {
				x.children.findPreamble
			}
		].flatten
	}
	
	private static def Iterable<IGeneratorNode> cleanNullChildren(Iterable<IGeneratorNode> nodes) {
		for(node : nodes) {
			if(node instanceof CompositeGeneratorNode) {
				val newChildren = cleanNullChildren(node.children.filterNull.toList)

				node.children.clear
				node.children.addAll(newChildren)
			}
		}
		nodes
	}
	
	static def CompositeGeneratorNode cleanNullChildren(CompositeGeneratorNode fragment) {
		val newChildren = new LinkedList;
		newChildren.addAll(fragment.children);
		newChildren.cleanNullChildren;
		
		fragment.children.clear();
		fragment.children.addAll(newChildren);
		fragment;
	}
	
}